(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
在前面幾篇中,當我們使用React component時,多半是這樣使用的:
<元素名稱/>
然而在一開始的時候,我們提到過像這樣的使用方法也是可以的:
<元素名稱> (其他的東西) </元素名稱>
在以前使用純html語法時,我們也常常用這樣巢狀的方式包住多個元素。那麼我們要怎麼在自製元素中使用包在中間的「其他東西」呢?
在react component中,我們把包在標籤中間的東西,稱為children。
children是props之一,所以當使用的children改變時,畫面也會重繪。接續上一篇的程式碼來練習使用children。原本我們在App.js中完成了一個有按鍵的元件,並在index.js使用。
import React from 'react';
function App(props){
return(
<button> {props.name} </button>
);
}
export default App;
現在,我們試著來在index.js中,用夾在<App>
標籤中的內容來設定<button></button>
中間的文字。
請把index.js中的<App/>
改成<App></App>
,並在兩個標籤中間加入文字。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<div>
<App> 在index.js中設定文字 </App>
</div>,
document.getElementById('root')
);
把App.js的render函式中button標籤內的文字移除。
function App(props){
return(
<button> </button>
);
}
在App.js函式中button標籤內使用children。因為children是props之一,所以使用方法為props.children
。
function App(props){
return(
<button> {props.children} </button>
);
}
export default App;
執行結果:
自己覺得最常用到的地方是做固定的Layout(EX: nav列)
例如,這是我亂做的破產版FB導覽列:
import React from 'react';
const Layout=(props)=>{
const navStyle={
position:"fixed",
top:"0",
left:"0",
width:"100%",
height:"43px",
backgroundColor:"rgb(66, 103, 178)",
display:"flex",
alignItems:"center"
};
const iconStyle={
marginLeft:"10%",
height:"25px",
width:"25px",
borderRadius:"1px",
backgroundColor:"white"
};
const inputStyle={
marginLeft:"5px",
padding:"0px 7px",
height:"25px",
width:"28%",
borderRadius:"2px",
border:"none",
backgroundImage:"url('https://cdn1.iconfinder.com/data/icons/hawcons/32/698627-icon-111-search-512.png')",
backgroundPosition:"97% 50%",
backgroundSize:"auto 80%",
backgroundRepeat:"no-repeat"
};
return(
<div>
<div className="nav-bar" style={navStyle}>
<div className="icon" style={iconStyle}>
<img style={{height:"120%"}} src="https://www.logospng.com/images/48/facebook-logo-fb-sketched-sketch-icon-48978.png" alt="icon"/>
</div>
<input placeholder="搜尋" style={inputStyle}/>
</div>
<div className="index-container" style={{marginTop:"43px"}}>
{props.children}
</div>
</div>
);
}
export default Layout;
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import Layout from "./Layout";
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<div>
<Layout>
<App>在index.js中設定文字</App>
</Layout>
</div>,
document.getElementById('root')
);
serviceWorker.unregister();
搭配後面會講的state
或是react-router-dom
去控制children
的內容,就能做出最小更動畫面的效果。另外,在設計component時,也能用children把架構拆成兩層,做出相當彈性、直覺又實用的架構(例如: boostrap中的List & ListItem)
跟上一篇的最後一樣,我還是先提過一下children間的溝通方法,後面會再整理一次。
children之間或是children和包住children的元素溝通方法相當於在「同一父元件中兩個子元件的溝通」。假設今天我們今天在A元件中,使用了B元件,B元件中的children是C和D元件。那麼當B要傳東西給C時,我們就要把A當作溝通橋梁:
- 用A的state來綁定C的props,在A中定義一個改變該state的函式
- 把該函式綁在B上
- 在B元件呼叫綁定在props上的A元件函式
- A元件的函式修改了state
- C元件的props就會被改變
C和D之間如果要溝通也是用同樣的方法,後面會有一篇詳細講一次。
children的使用方法就講到這邊,接下來幾篇我們會先從class component開始,然後講state跟react hook的useState。
題外話:
自己以前在查鐵人賽文章的時候,不會30篇全看,而是只看自己需要的章節,所以才會把相關的東西都提一下。在安排主題順序的時候猶豫了很久,因為我希望把這系列的重心擺在讓新手step by step了解現代框架中對於元件的操作邏輯,所以步調放的很慢,我覺得自己可能是這次鐵人賽中講React的人裡面講最慢的吧哈哈。可是這樣就變成我每篇都在說這個會用到後面的東西,不知道這樣是不是件壞事就是了。
想請教一下,如果使用了 props.chidren ,要如何在 children 中再 props 進去 children 裡面呢?
example:
App.jsx
exprort default function App () {
return <>
<Card>
<Button />
</Card>
</>
}
Card.jsx
exprort default function Card ({children}) {
const length = list.length
const Item = () => <>{children}</>
return <>
{
length.map(el=> {
<Item key={el.id} data={el}/>
})
}
</>
}
這時候在 Button.jsx 裡面的 data 會獲取不到資料
Hi, 抱歉最近有點小忙,我先回答你這個case的解法,再來慢慢解釋原理
import Card from 'someWhere';
import Button from 'someWhere';
export default function App () {
return <Card element={Button} />
}
// 你的list應該是從某個地方取得的,我假設它的結構是這樣
const list = [
{ id: 'a' },
{ id: 'b' },
];
export default function Card ({ element }) {
const Element = element; //不可省略
return (
<>
{
list.map(el=> {
<Element key={el.id} data={el}/>
})
}
</>
);
}
非常非常感謝大大的回答,最近在自學 react 的路上都是看松鼠大大的文章XD
要說明的部份有點多,我一部份一部份講。
首先,map
是javascript array ES6後的原生內建函式,而你先是呼叫了這個函式來取得array長度,也就是length是一個integer
const length = list.length
但在length是integer的情況下,對他呼叫map是不行的,因為integer並沒有原生map函式
length.map(el=> {
<Item key={el.id} data={el}/>
})
接著是為什麼你的data無法傳給Button。在你的程式碼中,你對children包了一層
const Item = () => <>{children}</>
雖然Babel在編譯的時候是根據大寫來判斷是不是React component,但是在function component內定義的function會被認定為是函式,而不是React component。所以這也是為什麼在我提供的解答中改成了
export default function Card ({ element }) {
const Element = element; //不可省略
因為你想做的事情是讓父層去決定Card要使用的React component。
然後是為什麼解答不是用children
而是用一般的props來接收要用的React component。children
的用法比較接近於設計模式中的裝飾者模式,大致上的概念是對於使用children
元件來說,該元件並不會去操作、控致children
的行為,而是對children
進行包裝。例如在我的教學文中,固定的Layout只會去包覆children、加上導覽列。
但是在你的case,我們必須要把資料list和父層傳入的component加工製成新UI,也就是父層傳入的component的行為會受到在Card綁定的props控制。這樣的需求會更接近於「仰賴注入」加上設計模式中的工廠模式(透過同樣的參數去產生不同的元件),所以透過一般的props傳入語意會更明確。react-router的<Route>
就是用同樣的方法。
最後是要注意Babel必須要看到大寫開頭才會知道要轉成React.createElement
(16以前)或是React._jsx
(17以後)。
export default function Card ({ element }) {
// 無法編譯成功的版本
return <element/>
// 正確的版本
const Element = element;
return <Element/>
}
以上是所有的解釋,有點複雜,建議可以去看看Design Pattern,可以幫助理解React在做什麼